import { bootEvent } from '../boot';
import path, { basename } from 'path';
import os from 'os';
import fs from 'fs';
import { makeLabelLogger } from '../util/logger';
import * as express from 'express';
import { syncRedisCommon, msgEvent } from './smain';
import { Client } from 'ssh2';
import { Socket } from 'net';
import { VERSION_NOW } from '../service/api/user';
import { SSHClientEx } from '../util/sshclient';
import { sleep } from '../util/tools';
import { Config } from './../app';

let { llog } = makeLabelLogger('mbsync-userservermgr');
let remoteUser = 'ec2-user';
remoteUser = 'root';

bootEvent.on('appInit', async function(app: express.Application) {
	llog('init...');

	app.use(require('body-parser').json());
	app.set('views', path.join(__dirname, '../../../html')); // 需按编译后的路径算，在dist里多了一层
	app.set('view engine', 'ejs');
	app.get('/', (req, res) => {
		res.redirect('/html/sync.html');
	});
	app.use('/html', express.static('./html'));
	app.get('/getServerList', getServerList);
	app.post('/addServer', addServer);
	app.post('/removeServer', removeServer);
	app.post('/updateServer', updateServer);
	app.post('/setDockerRegistry', setDockerRegistry);

	msgEvent.on('heartbeat', onHeartbeat);

	rebuildSSHChannels();
});

let renderIndex = async (req, res) => {
	// console.log('render index ');
	let processedValues = await syncRedisCommon.mget('processed:total', 'processed:fromLaunch');
	console.log(processedValues);
	res.render('index', {
		path: __dirname,
		processedValues,
	});
};

let addServer = async function(req: express.Request, res: express.Response) {
	llog('addServer', req.body);
	let remoteIP = req.body.ip;

	let serverKey = `UserServer:${req.body.name}`;
	let skeys = await syncRedisCommon.keys(serverKey);
	if (skeys.length > 0) {
		res.json({ err: '服务器名已存在！' });
		return;
	}

	let err = await sshCheckServer(req.body.name, remoteIP);
	if (err) {
		res.json({ err });
		return;
	}
	await syncRedisCommon.hmset(serverKey, ['ip', req.body.ip]);

	let sc = SSHClientEx.create(req.body.name, remoteUser, remoteIP, tunnel_rsa_key);
	Config.getUserServerForwardPorts().forEach(fp => {
		sc.addRemoteForward(fp.remotePort, fp.destHost, fp.destPort);
	});
	res.json({ ok: true });
};

let updateServer = async function(req: express.Request, res: express.Response) {
	llog('updateServer', req.body);

	let serverName = req.body.name;
	let serverKey = `UserServer:${serverName}`;
	let skeys = await syncRedisCommon.keys(serverKey);
	if (!skeys) {
		res.json({ err: '服务器不存在！' });
		return;
	}

	let remoteIP = await syncRedisCommon.hmget(serverKey, 'ip');
	let sshClient = SSHClientEx.get(serverName);
	if (sshClient) {
		llog('update sshclient', serverName, sshClient.isConnecting());
		if (sshClient.isConnecting()) {
			res.json({ err: '正在重连中' });
			return;
		}
		sshClient.reconnect();
	} else {
		res.json({ err: '服务器并未建立连接' });
	}

	res.json({ ok: true });
};

let removeServer = async function(req: express.Request, res: express.Response) {
	llog('removeServer', req.body);
	let serverName = req.body.name;
	let serverKey = `UserServer:${serverName}`;
	await syncRedisCommon.del(serverKey);
	SSHClientEx.delete(serverName);
	res.json({ ok: true });
};

let getServerList = async function(req: express.Request, res: express.Response) {
	let skeys = await syncRedisCommon.keys('UserServer:*');

	let servers = await Promise.all(
		skeys.map(async skey => {
			let keys = ['ip', 'lastHeartbeatRecv'];
			let props = await syncRedisCommon.hmget(skey, ...keys);
			let info = {};
			keys.forEach((k, i) => {
				info[k] = props[i];
			});
			let sname = skey.split(':')[1];
			let sc = SSHClientEx.get(sname);
			return {
				name: sname,
				sshInfo: {
					connected: sc ? sc.connected : false,
					channels: sc ? sc.remoteChannels.map(v => v[1]) : [],
				},
				...info,
				// lastHeartbeatRecv: lastHeartbeatRecv.map(v => parseInt(v)),
			};
		}),
	);

	res.json(servers);
};

let setDockerRegistry = async function(req: express.Request, res: express.Response) {
	llog('set dockerRegistry', req.body.value);
	try {
		await syncRedisCommon.set('config:dockerRegistry', req.body.value);
		res.json({ ok: true });
	} catch (e) {
		llog('error', e);
		res.json({ err: e.message, code: e.code });
	}
};

let onHeartbeat = async function(action) {
	llog('heartbeat', action);
	// prettier-ignore
	syncRedisCommon.hmset(`UserServer:${action.srcHost}`,
		['lastHeartbeatRecv', Date.now().toString()],
		['lastHeartbeat', action.time],
	);
};

let sshCheckServer = async (name: string, remoteIP: string) => {
	let sshClient = SSHClientEx.create(name, remoteUser, remoteIP, tunnel_rsa_key);
	let myFill: (err?: string) => void;
	let pro = new Promise(fill => {
		myFill = fill;
	});

	sshClient
		.on('error', function(e) {
			llog('ssh connect error:', e.message);
			myFill(e.message);
		})
		.on('ready', async function() {
			let err,
				code,
				stdout = '',
				stderr = '';

			let dockerRegistry = await syncRedisCommon.get('config:dockerRegistry');
			if (!dockerRegistry) return myFill('dockerRegistry not set!');

			[code, stdout, stderr] = await sshClient.execute(`docker network inspect bridge --format='{{( index .IPAM.Config 0).Gateway}}'`);
			if (code != 0) return myFill('获取docker网桥配置出错:' + (stdout || stderr));
			let docker0ip = stdout.trim();
			// llog('docker bridge', stdout);
			// myFill('test');

			// 此文件应在制作sync image前生成好放到html目录下
			let dcpName = 'mb-user';
			let dcfPath = 'mb-user.dc';
			let content = fs.readFileSync('./html/mb-user.dc').toString();
			content = content.replace(/(image: ).+(\/.+)/g, `$1${dockerRegistry}$2`);
			content = content.replace(/"(dockerhost:).+"/g, `"$1${docker0ip}"`);
			// llog('replace dc', content);
			// myFill('test');
			let dcfSrcPath = './html/mb-user.final';
			fs.writeFileSync(dcfSrcPath, content);

			err = await sshClient.sshUpload(dcfSrcPath, dcfPath);
			if (err) return myFill('upload docker-compose failed:' + err);

			[code, stdout, stderr] = await sshClient.execute(`docker-compose -f ${dcfPath} pull`);
			if (code != 0) return myFill('拉新镜像出错:' + (stdout || stderr));

			[code, stdout] = await sshClient.execute(`docker-compose -p ${dcpName} -f ${dcfPath} down`);
			if (code != 0) return myFill('停止老程序出错:' + stdout);

			[code, stdout] = await sshClient.execute(`docker volume rm -f ${dcpName}_assets`);
			if (code != 0) return myFill('删除老数据出错:' + stdout);

			[code, stdout] = await sshClient.execute(`docker-compose -p ${dcpName} -f ${dcfPath} up -d`);
			if (code != 0) return myFill('启动新程序出错:' + stdout);

			[code, stdout] = await sshClient.sshSetUserServerKeyValue('config:hostname', name);
			if (code != 0) return myFill('设置同步源出错:' + stdout);

			sshClient.end();
			myFill();
		});
	let err = await pro;
	if (err) SSHClientEx.delete(name);
	return err;
};

let rebuildSSHChannels = async () => {
	llog('rebuildSSHChannels');
	let skeys = await syncRedisCommon.keys('UserServer:*');
	skeys.forEach(async skey => {
		let [ip] = await syncRedisCommon.hmget(skey, 'ip');
		let sname = skey.split(':')[1];
		llog('rebuildSSHChannels', skey, ip);
		if (ip) {
			let sc = SSHClientEx.create(sname, remoteUser, ip, tunnel_rsa_key);
			sc.once('ready', async () => {
				let [code, stdout] = await sc.sshSetUserServerKeyValue('config:synchost', os.hostname());
			});
			Config.getUserServerForwardPorts().forEach(fp => {
				sc.addRemoteForward(fp.remotePort, fp.destHost, fp.destPort);
			});
		}
	});
};

let tunnel_rsa_key = `-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEApFkL/q4aWiBwjgljw2QG/58CR6rsS8cFgiFdxnsPMhwilA/B
oKtLXV/JDuXlbNZWW90bYXtjwqW2F6J8O+oU8CztgmBukBP+8hgIgWtGHuYaHmpD
OJ5/nG9WmNEaQJD1MGzBx3UD/WxA/MnpYqNX6xQhfSG9jQJ0nirAfNixZQNdi+Tj
Thnvwdb2g33AxPonc+osUr0XniCnkTC3Q5TbW0mb/xmJQDflQbQYH78GlGJNngYX
J3LLrIAumWhjXAA95ICId3kswMTUvYzHLs8BMfHr5RjkAn5nNZJ/kwoUTXix7Xjq
Jq/u2DK9JbHLsg9kBBi1BuzWJfUQV8WHRQnAwQIDAQABAoIBAAul+BCr/Fgo9nFK
vxRpMYnKllnIBol0aLKJdOENdc1Cazz9ROcDXQsYL+LrbnBhXKuUJ02odjCkmBxi
utVwSN2hAZB9DzkNAcPc6jVqCgVEn9NKCsuniDnpNkUMhQTQgWBWRQHDDG5Yzt5b
3TvNgfy6Z4k76/hjlSpks1Hl9eTH1DUBKpqgK1itfU6tkCDpt77hUulpjAI6NdzK
xAiTFLk7Ua2FqMzT8nzWMcZeF2x6o8Lz1TYhwnJILQScjzUD5CKaqgbWm9cP5R+N
n5vlg1rB151WiPVzVdtFqOGdwthFC4GHFLtXgJFgsFiyZbSWcCt2KQAw/ExcJbKV
zbz9cqUCgYEAzYPcLya1w09tpwqCy+NNrPVq9Qmwz98/wEFUoUUNsZCTPuP/rUY3
FAVfjptfnD6v8LSGMkHpluxRcb9He78nBx4qhn+rCGwOZuuMJ8E5s38tUO8UjVre
vGCAuGXk3n+NDR4YrFiNyEY9f7hMKz3Xv3PkjOvl0qtOurtcBtp+OaMCgYEAzLhR
6ldoQOb8BarBJ1a+BfPGDDOVbvGD7EWhEclypQ/8sWHnFRoXyDWDg885O8m0xk+J
QRpH3YEncj2hflCtR9UeCH+tzuyCK++Aed4P6oj7Qcc3vhpMNZLLjjq5DZH2rcMN
qvX3m78/KWZpaEttAW8sQAFYcZNtEZ3DJxbfiksCgYBnnksezuKoBd0gcpE94W4h
fV+fCnLGbCZFdesIoLtlwqIq/GPF5ngQtSDX0wjiy0yEDVD39/mhaacqtamB///a
e+AvDiGaznh4ddylkqvYD5A2lUSEfMN+sR1VW7KIaEEi0wDXrJJX3upVAgQNbVHh
KrqaProSfOLmDV856WHpBwKBgExkDzUZzq5BQxO+Rvst9BR2hZVx/jX661ZUGSlw
+VW8pHdwZ/FI3kz4Iw4hWmMD/m/tp/LiIqT2F8kaVFO1OSPP07gqJKpNOLm6Uj+n
bFsBKHjAJP3MBFoNX+W/5eO5bsoiOcxsoiu87o29SGIBmzhoyqNLnTwsXguvye2h
LGMHAoGAVBqF6Gar7HCFV1sA3BjxsWTFmI9ZEM4l0Ioufz7XEfXxmlwHTOVW/iLe
7wIErFK7dC8yTcUSOjJ2/1LJ8KVk7s1VRHSl1gFopsFfrrk8Ast/T2XdF9+NwuAJ
uZvb+n6OWC1mO4894U3j2hXpaD4D2yQ7nvFWaiq3Zi/4cfwoa2M=
-----END RSA PRIVATE KEY-----
`;

const shell = function(cwd, cmd, opt) {
	let child_process = require('child_process');
	try {
		llog(cmd);
		let opt_env = (opt && opt.env) || {};
		let r = child_process.execSync(cmd, {
			stdio: 'inherit',
			cwd,
			// env: { ...baseEnv, ...opt_env },
			...opt,
		});
		return r ? r.toString() : undefined;
	} catch (e) {
		if (opt && opt.noerror) {
			llog('ignore error', e.message);
		} else throw e;
	}
};
